#region Usings
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Validation;
using System.Linq;

using BK.Model;
using BK.Repositories.Abstract;

using Lib.General;
#endregion

namespace BK.Repositories.EF
{
    public abstract class BaseRepository<TEntity, TBaseEntity, TDataContext> : IRepository<TEntity>, IEfRepository
        where TBaseEntity : class, IBkEntity
        where TEntity : class, IBkEntity, TBaseEntity
        where TDataContext : DbContext
    {
        #region Readonly & Static Fields
        private readonly TDataContext _context;
        #endregion

        #region Fields
        private DbSet<TBaseEntity> _objectSet;
        #endregion

        #region C'tors
        public BaseRepository(TDataContext dataContext)
        {
            _context = dataContext;
        }
        #endregion

        #region Instance Methods
        public virtual void Add(TEntity entity)
        {
            ObjectSet.Add(entity);
        }
        #endregion

        #region Instance Properties
        public TDataContext Context
        {
            get { return _context; }
        }

        public DbSet<TBaseEntity> ObjectSet
        {
            get { return _objectSet ?? (_objectSet = _context.Set<TBaseEntity>()); }
        }
        #endregion

        #region IEfRepository Members
        DbContext IEfRepository.Context
        {
            get { return Context; }
        }
        #endregion

        #region IRepository<TEntity> Members
        void IRepository.Delete(object entity)
        {
            Delete((TEntity) entity);
        }

        public int DeleteAll()
        {
            IEnumerable<TEntity> all = GetAll();
            int counter = 0;
            foreach (TEntity entity in all)
            {
                _objectSet.Remove(entity);
                counter++;
            }
            Save();
            return counter;
        }

        IEnumerable IRepository.GetAll()
        {
            return GetAll();
        }

        public int Count()
        {
            return ObjectSet.Count();
        }

        object IRepository.GetById(int id)
        {
            return GetById(id);
        }

        public void Delete(int id)
        {
            Delete(GetById(id));
        }

        void IRepository.Save(object entity)
        {
            Save((TEntity) entity);
        }

        /// <summary>
        /// Saves all changes in a storage
        /// </summary>
        public void Save()
        {
            try
            {
                _context.SaveChanges();
            }
            catch(DbEntityValidationException)
            {
                IEnumerable<DbEntityValidationResult> dbEntityValidationResults = _context.GetValidationErrors();
                foreach (DbEntityValidationResult dbEntityValidationResult in dbEntityValidationResults)
                {
                    foreach (DbValidationError dbValidationError in dbEntityValidationResult.ValidationErrors)
                    {
                        Logger.Error(this, dbValidationError.ErrorMessage);
                    }
                }

                throw;
            }
        }

        /// <summary>
        /// Attaches <see cref="entity"/> to the context and then calls <see cref="BkDataContext.SaveChanges"/>
        /// </summary>
        /// <param name="entity">Instance of <see cref="TEntity"/> to be saved</param>
        public void Save(TEntity entity)
        {
            if (entity.Id != 0)
            {
                ObjectSet.Attach(entity);
                _context.Entry(entity).State = EntityState.Modified;
            }
            else
            {   
                ObjectSet.Add(entity);
            }
            Save();
        }

        public virtual void Delete(TEntity entity)
        {
            _objectSet.Remove(entity);
            _context.SaveChanges();
        }

        public IEnumerable<TEntity> GetAll()
        {
            return (from entity in ObjectSet.OfType<TEntity>() select entity).ToList();
        }

        public TEntity GetById(int id)
        {
            return (TEntity) ObjectSet.Single(e => e.Id == id);
        }
        #endregion
    }

    public class BaseRepository<TEntity, TDataContext> : BaseRepository<TEntity, TEntity, TDataContext>
        where TEntity : class, IBkEntity
        where TDataContext : DbContext
    {
        #region C'tors
        public BaseRepository(TDataContext dataContext)
            : base(dataContext)
        {
        }
        #endregion
    }

    public class BaseRepository<TEntity> : BaseRepository<TEntity, TEntity, BkDataContext>
        where TEntity : class, IBkEntity
    {
        #region C'tors
        public BaseRepository(BkDataContext dataContext)
            : base(dataContext)
        {
        }
        #endregion
    }
}